package demo2

import "fmt"

/*
	语言结构体（struct）
	1. Go 语言通过用自定义的方式形成新的类型，结构体是类型中带有成员的复合类型。
			Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。
	2. Go 语言中的类型可以被实例化，使用new或&构造的类型实例的类型是类型的指针。
	3. 结构体成员是由一系列的成员变量构成，这些成员变量也被称为“字段”。字段有以下特性：
			3.1 字段拥有自己的类型和值。
			3.2 字段名必须唯一
			3.3 字段的类型也可以是结构体，甚至是字段所在结构体的类型。
	4. Go 语言中没有“类”的概念，也不支持“类”的继承等面向对象的概念。
	5. Go 语言的结构体与“类”都是复合结构体，但 Go 语言中结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性。
	6. Go 语言不仅认为结构体能拥有方法，且每种自定义类型也可以拥有自己的方法。
*/

/*
	demo 1: 结构体定义
	1. 结构体是一种聚合的数据类型，是由零个或多个任意类型的值聚合成的实体。
	2. 每个值称为结构体的成员。Go语言的结构体（struct）和其他语言的类（class）有同等的地位，
		但 Go语言放弃了包括继承在内的大量面向对象特性，只保留了组合（composition）这个最基础的特性。
	3. 组合甚至不能算面向对象特性，因为在 C语言这样的过程式编程语言中，也有结构体，也有组合。组合只是形成复合类型的基础。
	4. 使用关键字 type 可以将各种基本类型定义为自定义类型，基本类型包括整型、字符串、布尔等。
		结构体是一种复合的基本类型，通过 type 定义为自定义类型后，使结构体更便于使用。
	5. 结构体的定义只是一种内存布局的描述，只有当结构体实例化时，才会真正地分配内存
*/

// Point 点
type Point struct {
	X int
	Y int
}

// Color 颜色
type Color struct {
	R, G, B byte // 同类型的变量也可以写在一行。颜色的红、绿、蓝 3 个分量可以使用 byte 类型表示
}

// Player 玩家
type Player struct {
	Name        string
	HealthPoint int
	MagicPoint  int
}

// Command 命令行
type Command struct {
	Name    string // 名称
	Var     *int   // 绑定的变量
	Comment string // 指令的注释
}

// People 人群
type People struct {
	name  string
	child *People
}

// Address 地址
type Address struct {
	Province    string
	City        string
	ZipCode     int
	PhoneNumber string
}

// newCommand1
func newCommand1() *Command {
	num1 := 10
	cmd1 := Command{
		Name:    "version",
		Var:     &num1,
		Comment: "version comment",
	}
	cmd1Ref := &cmd1
	return cmd1Ref
	// return cmd1
}

// newCommand2
func newCommand2(name string, varref *int, comment string) *Command {
	return &Command{
		Name:    name,
		Var:     varref,
		Comment: comment,
	}
}

// 打印消息类型, 传入匿名结构体
func printMsgType(msg *struct {
	id   int
	data string
}) {
	fmt.Println(msg)
	// 使用动词%T打印msg的类型
	fmt.Printf("%T\n", msg)
}

/*
	demo4: 模拟构造函数
	1. Go语言中没有提供构造函数相关的特殊机制，用户根据自己的需求，将参数使用函数传递到结构体构造参数中即可完成构造函数的任务。
*/

// Cat 猫
type Cat struct {
	Color string
	Name  string
}

// NewCatByName 多种方式创建和初始化结构体——模拟构造函数重载
func NewCatByName(name string) *Cat {
	return &Cat{
		Name: name,
	}
}

// NewCatByColor 按颜色
func NewCatByColor(color string) *Cat {
	return &Cat{
		Color: color,
	}
}

/* demo5: 模拟父级构造调用 */

// BlackCat 黑猫
type BlackCat struct {
	Cat // 嵌入 Cat 类似派生
}

// NewCat “构造基类”
func NewCat(name string) *Cat {
	return &Cat{
		Name: name,
	}
}

// NewBlankCat “构造子类”
func NewBlankCat(color string) *BlackCat {
	cat := &BlackCat{}
	cat.Color = color
	return cat
}

// TestDemo210 结构体
func TestDemo210() {
	/*
		demo2: 为结构体分配内存并初始化
		1. 结构体的定义只是一种内存布局的描述，只有当结构体实例化时，才会真正地分配内存。
			因此必须在定义结构体并实例化后才能使用结构体的字段。
		2. 实例化就是根据结构体定义的格式创建一份与格式一致的内存区域，结构体实例与实例间的内存是完全独立的。
		3. Go语言可以通过多种方式实例化结构体，根据实际需要可以选用不同的写法。
	*/
	var p Point
	p.X = 10
	p.Y = 20

	/*
		demo3: 创建指针类型的结构体
		1. Go语言中，还可以使用 new 关键字对类型（包括结构体、整型、浮点数、字符串等）进行实例化，
				结构体在实例化后会形成指针类型的结构体。格式如下：
				ins := new(T)
		2. 经过 new 实例化的结构体实例在成员赋值上与基本实例化的写法一致。
	*/
	tank := new(Player)
	tank.Name = "Santiiny"
	tank.HealthPoint = 100
	tank.MagicPoint = 100

	/*
		demo4: 取结构体的地址实例化
		1. 在 Go语言中，对结构体进行&取地址操作时，视为对该类型进行一次 new 的实例化操作。取地址格式如下：
			ins := &T{}
			T 表示结构体类型。, ins 为结构体的实例，类型为 *T，是指针类型。
		2. 取地址实例化是最广泛的一种结构体实例化方式。可以使用函数封装上面的初始化过程
	*/
	var version = 1
	cmd := &Command{}
	cmd.Name = "version"
	cmd.Var = &version
	cmd.Comment = "show version"

	cmd2 := newCommand2(
		"version",
		&version,
		"show version",
	)

	fmt.Println(cmd2)

	/*
		demo5: 初始化结构体的成员变量
			1. 结构体在实例化时可以直接对成员变量进行初始化。
			2. 初始化有两种形式：
				2.1 一种是字段“键值对”形式及多个值的列表形式。键值对形式的初始化适合选择性填充字段较多的结构体；
				2.2 多个值的列表形式适合填充字段较少的结构体。
	*/
	// demo1: 使用“键值对”初始化结构体
	person1 := &People{
		name: "Parent people",
		child: &People{
			name: "Child perple",
			child: &People{
				name: "this",
			},
		},
	}

	fmt.Println(person1)

	// demo2: 使用多个值的列表初始化结构体
	// 1. 必须初始化结构体的所有字段。
	// 2. 每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致。
	// 3. 键值对与值列表的初始化形式不能混用。
	addr1 := Address{
		"河南",
		"洛阳",
		471000,
		"654321",
	}

	fmt.Println(addr1)

	/*
		demo3: 初始化匿名结构体
		1. 匿名结构体没有类型名称，无须通过 type 关键字定义就可以直接使用。
		2. 匿名结构体的初始化写法由结构体定义和键值对初始化两部分组成。
				结构体定义时没有结构体类型名，只有字段和类型定义。键值对初始化部分由可选的多个键值对组成，
		3. 匿名结构体的类型名是结构体包含字段成员的详细描述。匿名结构体在使用时需要重新定义，造成大量重复的代码，因此开发中较少使用。
	*/

	// 实例化一个匿名结构体
	msg := &struct {
		// 定义部分
		id   int
		data string
	}{
		// 值得初始化
		101,
		"test",
	}
	printMsgType(msg)

}
